home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Graphics Plus
/
Graphics Plus.iso
/
msdos
/
viewers
/
pvquan16
/
quant
/
heckbert.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-11-30
|
17KB
|
568 lines
/************************************************************************
* *
* Copyright (c) 1991, Frank van der Hulst *
* All Rights Reserved *
* *
* Authors: *
* FvdH - Frank van der Hulst (Wellington, NZ) *
* *
* Versions: *
* V1.1 910626 FvdH - QUANT released for DBW_RENDER *
* V1.2 911021 FvdH - QUANT released for PoV Ray *
* V1.4 920303 FvdH - Ported to GNU *
* V1.6 921023 FvdH - Produce multi-image GIFs *
* - Port to OS/2 IBM C Set/2 *
* *
************************************************************************/
/*
* This software is copyrighted as noted below. It may be freely copied,
* modified, and redistributed, provided that the copyright notice is
* preserved on all copies.
*
* There is no warranty or other guarantee of fitness for this software,
* it is provided solely "as is". Bug reports or fixes may be sent
* to the author, who may or may not act on them as he desires.
*
* You may not include this software in a program or other software product
* without supplying the source, or without informing the end-user that the
* source is available for no extra charge.
*
* If you modify this software, you should include a notice giving the
* name of the person performing the modification, the date of modification,
* and the reason for such modification.
*/
/*
* colorquant.c
*
* Perform variance-based color quantization on a "full color" image.
* Author: Craig Kolb
* Department of Mathematics
* Yale University
* kolb@yale.edu
* Date: Tue Aug 22 1989
* Copyright (C) 1989 Craig E. Kolb
* $Id: colorquant.c,v 1.3 89/12/03 18:27:16 craig Exp $
*
* $Log: colorquant.c,v $
*
* Revision 1.4 91/06/26 16:00:00 Frank van der Hulst
* Ported to Turbo C;
* Call farmalloc rather than malloc
* Virtual memory added to swap box data to/from disk
* Rewritten in ANSI C
* Removed call to QuantHistogram() from colorquant, to allow two
* image files to create one palette
* Changed QuantHistogram() to read from file, rather than from an
* array
* Changed format of palette to conform with the VGA palette
*
* Revision 1.3 89/12/03 18:27:16 craig
* Removed bogus integer casts in distance calculation in makenearest().
*
* Revision 1.2 89/12/03 18:13:12 craig
* FindCutpoint now returns FALSE if the given box cannot be cut. This
* to avoid overflow problems in CutBox.
* "whichbox" in GreatestVariance() is now initialized to 0.
*
*/
#ifdef __TURBOC__
#include <mem.h>
#define HUGE 1.79e308
#endif
#include <math.h>
#include "quant.h"
#include "heckbert.h"
#define MAX(x,y) ((x) > (y) ? (x) : (y))
static unsigned long NPixels = 0L; /* total # of pixels */
static int neighbours[MAXCOLORS];
/*
* Compute the histogram of the image as well as the projected frequency
* arrays for the first world-encompassing box.
* We compute both the histogram and the proj. frequencies of
* the first box at the same time to save a pass through the
* entire image.
* The projected frequency arrays of the largest box are zeroed out as
* as part of open_box_file(), called from main().
*/
void QuantHistogram(Box *box)
{
unsigned long *rf, *gf, *bf;
UCHAR pixel[3];
rf = box->freq[RED];
gf = box->freq[GREEN];
bf = box->freq[BLUE];
while (get_pixel(pixel)) {
rf[pixel[RED]]++;
gf[pixel[GREEN]]++;
bf[pixel[BLUE]]++;
Histogram[(((pixel[RED]<<INPUT_BITS)|pixel[GREEN])<<INPUT_BITS)|pixel[BLUE]]++;
NPixels++;
}
}
/*
* Compute mean and weighted variance of the given box.
*/
void BoxStats(Box HUGE_PTR box)
{
int i, color;
unsigned long *freq;
double mean, var;
if(box->weight == 0) {
box->weightedvar = 0;
return;
}
box->weightedvar = 0.0;
for (color = 0; color < 3; color++) {
var = mean = 0;
i = box->low[color];
freq = &box->freq[color][i];
for (; i < box->high[color]; i++, freq++) {
mean += i * *freq;
var += i*i* *freq;
}
box->mean[color] = mean / box->weight;
box->weightedvar += var - box->mean[color]*box->mean[color]*box->weight;
}
box->weightedvar /= NPixels;
}
/*
* Return the number of the box in 'boxes' with the greatest variance.
* Restrict the search to those boxes with indices between 0 and n-1.
*/
int GreatestVariance(int n)
{
int i, whichbox = 0;
double max;
Box *box;
max = -1;
for (i = 0; i < n; i++) {
box = get_box_tmp(i);
if (box->weightedvar > max) {
max = box->weightedvar;
whichbox = i;
}
}
return whichbox;
}
/*
* Update projected frequency arrays for two boxes which used to be
* a single box.
*/
void UpdateFrequencies(Box HUGE_PTR box1, Box HUGE_PTR box2)
{
unsigned long myfreq, HUGE_PTR h;
int b, g, r;
int roff;
memset(box1->freq[0], 0, IN_COLOURS * sizeof(unsigned long));
memset(box1->freq[1], 0, IN_COLOURS * sizeof(unsigned long));
memset(box1->freq[2], 0, IN_COLOURS * sizeof(unsigned long));
for (r = box1->low[0]; r < box1->high[0]; r++) {
roff = r << INPUT_BITS;
for (g = box1->low[1];g < box1->high[1]; g++) {
b = box1->low[2];
h = Histogram + (((roff | g) << INPUT_BITS) | b);
for (; b < box1->high[2]; b++) {
if ((myfreq = *h++) == 0)
continue;
box1->freq[0][r] += myfreq;
box1->freq[1][g] += myfreq;
box1->freq[2][b] += myfreq;
box2->freq[0][r] -= myfreq;
box2->freq[1][g] -= myfreq;
box2->freq[2][b] -= myfreq;
}
}
}
}
/*
* Compute the 'optimal' cutpoint for the given box along the axis
* indicated by 'color'. Store the boxes which result from the cut
* in newbox1 and newbox2.
*/
int FindCutpoint(Box HUGE_PTR box, int color, Box HUGE_PTR newbox1, Box HUGE_PTR newbox2)
{
double u, v, max;
int i, maxindex, minindex, cutpoint;
unsigned long optweight, curweight;
if (box->low[color] + 1 == box->high[color])
return FALSE; /* Cannot be cut. */
minindex = (int)((box->low[color] + box->mean[color]) * 0.5);
maxindex = (int)((box->mean[color] + box->high[color]) * 0.5);
cutpoint = minindex;
optweight = box->weight;
curweight = 0.;
for (i = box->low[color] ; i < minindex ; i++)
curweight += box->freq[color][i];
u = 0.;
max = -1;
for (i = minindex; i <= maxindex ; i++) {
curweight += box->freq[color][i];
if (curweight == box->weight)
break;
u += (double)(i * box->freq[color][i]) / box->weight;
v = ((double)curweight / (box->weight-curweight)) *
(box->mean[color]-u)*(box->mean[color]-u);
if (v > max) {
max = v;
cutpoint = i;
optweight = curweight;
}
}
cutpoint++;
*newbox1 = *newbox2 = *box;
newbox1->weight = optweight;
newbox2->weight -= optweight;
newbox1->high[color] = cutpoint;
newbox2->low[color] = cutpoint;
UpdateFrequencies(newbox1, newbox2);
BoxStats(newbox1);
BoxStats(newbox2);
return TRUE; /* Found cutpoint. */
}
/*
* Cut the given box. Returns TRUE if the box could be cut, FALSE otherwise.
*/
int CutBox(Box HUGE_PTR box, Box HUGE_PTR newbox)
{
int i;
double totalvar[3];
static Box newboxes[3][2]; /* Only used by CutBox, but don't want it on stack */
if (box->weightedvar == 0. || box->weight == 0)
/*
* Can't cut this box.
*/
return FALSE;
/*
* Find 'optimal' cutpoint along each of the red, green and blue
* axes. Sum the variances of the two boxes which would result
* by making each cut and store the resultant boxes for
* (possible) later use.
*/
for (i = 0; i < 3; i++) {
if (FindCutpoint(box, i, &newboxes[i][0], &newboxes[i][1]))
totalvar[i] = newboxes[i][0].weightedvar +
newboxes[i][1].weightedvar;
else
totalvar[i] = HUGE;
}
/*
* Find which of the three cuts minimized the total variance
* and make that the 'real' cut.
*/
if (totalvar[RED] <= totalvar[GREEN] &&
totalvar[RED] <= totalvar[BLUE]) {
*box = newboxes[RED][0];
*newbox = newboxes[RED][1];
} else if (totalvar[GREEN] <= totalvar[RED] &&
totalvar[GREEN] <= totalvar[BLUE]) {
*box = newboxes[GREEN][0];
*newbox = newboxes[GREEN][1];
} else {
*box = newboxes[BLUE][0];
*newbox = newboxes[BLUE][1];
}
return TRUE;
}
/*
* Iteratively cut the boxes.
*/
CutBoxes(int colors)
{
int curbox, varbox;
Box *box = get_box(0);
box->low[RED] = box->low[GREEN] = box->low[BLUE] = 0;
box->high[RED] = box->high[GREEN] = box->high[BLUE] = IN_COLOURS;
box->weight = NPixels;
BoxStats(box);
free_box(0);
printf("%d Boxes: cutting box: ", colors);
for (curbox = 1; curbox < colors; curbox++) {
printf("%3d\b\b\b", curbox);
varbox = GreatestVariance(curbox);
if (CutBox(get_box(varbox), get_box(curbox)) == FALSE)
break;
free_box(curbox);
free_box(varbox);
}
printf("Done\n");
return curbox;
}
/*
* Make the centroid of "boxnum" serve as the representative for
* each color in the box.
*/
void SetRGBmap(int boxnum, Box *box, int bits)
{
int r, g, b;
for (r = box->low[RED]; r < box->high[RED]; r++) {
for (g = box->low[GREEN]; g < box->high[GREEN]; g++) {
for (b = box->low[BLUE]; b < box->high[BLUE]; b++) {
RGBmap[(((r<<bits)|g)<<bits)|b]=(char)boxnum;
}
}
}
}
/*
* In order to minimize our search for 'best representative', we form the
* 'neighbors' array. This array contains the number of the boxes whose
* centroids *might* be used as a representative for some color in the
* current box. We need only consider those boxes whose centroids are closer
* to one or more of the current box's corners than is the centroid of the
* current box. 'Closeness' is measured by Euclidean distance.
*/
int getneighbors(int num, int colors)
{
int i, j;
Box *bp;
double dist, LowR, LowG, LowB, HighR, HighG, HighB, ldiff, hdiff;
bp = get_box_tmp(num);
ldiff = bp->low[RED] - bp->mean[RED];
ldiff *= ldiff;
hdiff = bp->high[RED] - bp->mean[RED];
hdiff *= hdiff;
dist = MAX(ldiff, hdiff);
ldiff = bp->low[GREEN] - bp->mean[GREEN];
ldiff *= ldiff;
hdiff = bp->high[GREEN] - bp->mean[GREEN];
hdiff *= hdiff;
dist += MAX(ldiff, hdiff);
ldiff = bp->low[BLUE] - bp->mean[BLUE];
ldiff *= ldiff;
hdiff = bp->high[BLUE] - bp->mean[BLUE];
hdiff *= hdiff;
dist += MAX(ldiff, hdiff);
dist = sqrt(dist);
/*
* Loop over all colors in the colormap, the ith entry of which
* corresponds to the ith box.
*
* If the centroid of a box is as close to any corner of the
* current box as is the centroid of the current box, add that
* box to the list of "neighbors" of the current box.
*/
HighR = (double)bp->high[RED] + dist;
HighG = (double)bp->high[GREEN] + dist;
HighB = (double)bp->high[BLUE] + dist;
LowR = (double)bp->low[RED] - dist;
LowG = (double)bp->low[GREEN] - dist;
LowB = (double)bp->low[BLUE] - dist;
for (i = j = 0; i < colors; i++) {
bp = get_box_tmp(i);
if (LowR <= bp->mean[RED] && HighR >= bp->mean[RED] &&
LowG <= bp->mean[GREEN] && HighG >= bp->mean[GREEN] &&
LowB <= bp->mean[BLUE] && HighB >= bp->mean[BLUE])
neighbours[j++] = i;
}
return j; /* Return the number of neighbors found. */
}
/*
* Assign representative colors to every pixel in a given box through
* the construction of the NearestColor array. For each color in the
* given box, we look at the list of neighbors passed to find the
* one whose centroid is closest to the current color.
*/
void makenearest(int boxnum, int nneighbors, int bits)
{
int n, b, g, r;
double rdist, gdist, bdist, dist, mindist;
int which, *np;
Box *box, *bp;
box = get_box(boxnum);
for (r = box->low[RED]; r < box->high[RED]; r++) {
for (g = box->low[GREEN]; g < box->high[GREEN]; g++) {
for (b = box->low[BLUE]; b < box->high[BLUE]; b++) {
/*
* The following two lines should be commented out if the RGBmap is going
* to be used for images other than the one given.
*/
if (Histogram[(((r<<bits)|g)<<bits)|b] == 0)
continue;
mindist = HUGE;
/*
* Find the colormap entry which is
* closest to the current color.
*/
np = neighbours;
for (n = 0; n < nneighbors; n++, np++) {
bp = get_box_tmp(*np);
rdist = r - bp->mean[RED];
gdist = g - bp->mean[GREEN];
bdist = b - bp->mean[BLUE];
dist = rdist*rdist + gdist*gdist + bdist*bdist;
if (dist < mindist) {
mindist = dist;
which = *np;
}
}
/*
* The colormap entry closest to this
* color is used as a representative.
*/
RGBmap[(((r<<bits)|g)<<bits)|b] = which;
}
}
}
free_box(boxnum);
}
/*
* Form colormap and NearestColor arrays.
*/
void find_colors(int colors, int bits)
{
int i;
int num;
/*
* Form map of representative (nearest) colors.
*/
printf("Mapping colours for box: ");
for (i = 0; i < colors; i++) {
printf("%3d\b\b\b", i);
/*
* Create list of candidate neighbors and
* find closest representative for each
* color in the box.
*/
num = getneighbors(i, colors);
makenearest(i, num, bits);
}
printf("Done\n");
}
/*
* Compute RGB to colormap index map.
*/
void ComputeRGBMap(int colors, int bits, int fast)
{
int i;
if (fast) {
/*
* The centroid of each box serves as the representative
* for each color in the box.
*/
for (i = 0; i < colors; i++)
SetRGBmap(i, get_box_tmp(i), bits);
} else
/*
* Find the 'nearest' representative for each pixel.
*/
find_colors(colors, bits);
}
/*
* Perform variance-based color quantization on a 24-bit image.
*
* Input consists of:
* in_file Pointer to file containing of red, green and blue pixel
* intensities stored as unsigned characters.
* The color of the ith pixel is given consecutive bytes, in the
* order red, then green, then blue. Only the LS 4 bits are used.
* 0 indicates zero intensity, 15 full intensity.
* pixels The length of the red, green and blue arrays
* in bytes, stored as an unsigned long int.
* colormap Points to the colormap. The colormap
* consists of red, green and blue arrays.
* The red/green/blue values of the ith
* colormap entry are given respectively by
* colormap[0][i], colormap[1][i] and
* colormap[2][i]. Each entry is an unsigned char.
* colors The number of colormap entries, stored
* as an integer.
* bits The number of significant bits in each entry
* of the red, green and blue arrays. An integer.
* rgbmap An array of unsigned chars of size (2^bits)^3.
* This array is used to map from pixels to
* colormap entries. The 'prequantized' red,
* green and blue components of a pixel
* are used as an index into rgbmap to retrieve
* the index which should be used into the colormap
* to represent the pixel. In short:
* index = rgbmap[(((r << bits) | g) << bits) | b];
* fast If non-zero, the rgbmap will be constructed
* quickly. If zero, the rgbmap will be built
* much slower, but more accurately. In most
* cases, fast should be non-zero, as the error
* introduced by the approximation is usually
* small. 'Fast' is stored as an integer.
* Cfactor Conversion factor.
*
* colorquant returns the number of colors to which the image was
* quantized.
*/
int colorquant(int colors, int bits, int fast, double Cfactor)
{
int i, /* Counter */
OutColors; /* # of entries computed */
Box *box;
OutColors = CutBoxes(colors);
/*
* We now know the set of representative colors. We now
* must fill in the colormap and convert the representatives
* from their 'prequantized' range to 0-FULLINTENSITY.
*/
for (i = 0; i < OutColors; i++) {
box = get_box_tmp(i);
palette[i][RED] = (char)(box->mean[RED] * Cfactor + 0.5);
palette[i][GREEN] = (char)(box->mean[GREEN] * Cfactor + 0.5);
palette[i][BLUE] = (char)(box->mean[BLUE] * Cfactor + 0.5);
}
ComputeRGBMap(OutColors, bits, fast);
return OutColors; /* Return # of colormap entries */
}
int pal_index(UCHAR *pixel)
{
return RGBmap[(((pixel[RED]<<INPUT_BITS)|pixel[GREEN])<<INPUT_BITS)|pixel[BLUE]];
}